package com.tendory.common.per;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;

import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Permission {
    private static final String TAG = "Permission";

    private Object object;
    private int requestCode;
    private String[] permissions;
    private ArrayList<String> deniedPermissions;

    interface DestroyListener {
        void onDestroy();
    }
    private DestroyListener destroyListener;
    private PermissionsCallbacks callbacks;

    public Permission(Object o) {
        if (o == null)
            throw new IllegalArgumentException("The argument can not be null.");
        this.object = o;
    }

    public Permission requestCode(int requestCode) {
        this.requestCode = requestCode;
        return this;
    }

    public int getRequestCode() {
        return requestCode;
    }

    Permission setDestroyListener(DestroyListener destroyListener) {
        this.destroyListener = destroyListener;
        return this;
    }

    public Permission setPermissionsCallbacks(PermissionsCallbacks callbacks) {
        this.callbacks = callbacks;
        return this;
    }

    public Permission permission(String... permissions) {
        if (permissions == null)
            throw new IllegalArgumentException("The permissions can not be null.");
        this.permissions = permissions;
        return this;
    }

    public void request() {
        checkCallingObjectSuitability(object);
        if (permissions.length == 0/* || hasPermissions(getActivity(object), permissions)*/) {
            permissionsGranted();
            return;
        }
        String[] deniedPerms = getDeniedPermissions(object, permissions);
        if (deniedPerms.length == 0) { // Denied permissions size > 0.
            permissionsGranted();
            return;
        }

        boolean shouldShowRationale = false;
        for (String perm : deniedPerms) {
            shouldShowRationale = shouldShowRationale || shouldShowRequestPermissionRationale(object, perm);
        }
        if (shouldShowRationale) {
            Activity activity = getActivity(object);
            if (null == activity) {
                if (destroyListener != null) {
                    destroyListener.onDestroy();
                }
                return;
            }
            onShowRationaleDialog(activity, object, deniedPerms, requestCode);
        } else {
            executePermissionsRequest(object, deniedPerms, requestCode);
        }
    }

    protected void onShowRationaleDialog(Activity activity, Object o, String[] perms, int requestCode) {
        executePermissionsRequest(o, perms, requestCode);
    }

    protected boolean onShowRationaleDialog2(Activity activity, String[] perms) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
        intent.setData(uri);
        startAppSettingsScreen(object, intent);
        //permissionsDenied();
        return true;
    }

    protected void permissionsGranted() {
        if (callbacks != null) {
            callbacks.onPermissionsGranted(requestCode, Arrays.asList(permissions));
        }
        if (destroyListener != null) {
            destroyListener.onDestroy();
        }
    }

    protected void permissionsDenied() {
        if (callbacks != null) {
            callbacks.onPermissionsDenied(requestCode, deniedPermissions);
        }
        if (destroyListener != null) {
            destroyListener.onDestroy();
        }
    }

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, Object object) {
        checkCallingObjectSuitability(object);

        ArrayList<String> granted = new ArrayList<>();
        ArrayList<String> denied = new ArrayList<>();
        for (int i = 0; i < permissions.length; i++) {
            String perm = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                granted.add(perm);
            } else {
                denied.add(perm);
            }
        }

        // Report granted permissions, if any.
//        if (!granted.isEmpty()) {
//            // Notify callbacks
//            if (mGrantedListener != null) {
//                mGrantedListener.onPermissionsGranted(requestCode, granted);
//            }
//        }

        // Report denied permissions, if any.
        if (!denied.isEmpty()) {
            deniedPermissions = denied;
            if (checkDeniedPermissionsNeverAskAgain(object, denied)) {
                return;
            }
            permissionsDenied();
        } else {
            permissionsGranted();
        }
    }

    public boolean checkDeniedPermissionsNeverAskAgain(final Object object, List<String> deniedPerms) {
        boolean shouldShowRationale;
        for (String perm : deniedPerms) {
            shouldShowRationale = shouldShowRequestPermissionRationale(object, perm);
            if (!shouldShowRationale) {
                final Activity activity = getActivity(object);
                if (null == activity) {
                    permissionsDenied();
                    return true;
                }
                return onShowRationaleDialog2(activity, new String[] {perm});
            }
        }
        return false;
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data, Object object) {
        checkCallingObjectSuitability(object);
        final Activity activity = getActivity(object);
        if (null == activity) {
            permissionsDenied();
            return;
        }
        if (hasPermissions(activity, permissions)) {
            permissionsGranted();
        } else {
            //permissionsDenied();
            request();
        }
    }

    @TargetApi(11)
    private void startAppSettingsScreen(Object object, Intent intent) {
        if (object instanceof Activity) {
            ((Activity) object).startActivityForResult(intent, requestCode);
        } else if (object instanceof Fragment) {
            ((Fragment) object).startActivityForResult(intent, requestCode);
        } else if (object instanceof android.app.Fragment) {
            ((android.app.Fragment) object).startActivityForResult(intent, requestCode);
        }
    }

    private static void checkCallingObjectSuitability(Object object) {
        // Make sure Object is an Activity or Fragment
        boolean isActivity = object instanceof Activity;
        boolean isSupportFragment = object instanceof Fragment;
        boolean isAppFragment = object instanceof android.app.Fragment;
        boolean isMinSdkM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;

        if (!(isSupportFragment || isActivity || (isAppFragment && isMinSdkM))) {
            if (isAppFragment) {
                throw new IllegalArgumentException(
                        "Target SDK needs to be greater than 23 if caller is android.app.Fragment");
            } else {
                throw new IllegalArgumentException("Caller must be an Activity or a Fragment.");
            }
        }
    }
    private static boolean hasPermissions(Context context, String... perms) {
        // Always return true for SDK < M, let the system deal with the permissions
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(TAG, "hasPermissions: API version < M, returning true by default");
            return true;
        }

        for (String perm : perms) {
            boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) ==
                    PackageManager.PERMISSION_GRANTED);
            if (!hasPerm) {
                return false;
            }
        }
        return true;
    }

    @TargetApi(11)
    private static Activity getActivity(Object object) {
        if (object instanceof Activity) {
            return ((Activity) object);
        } else if (object instanceof Fragment) {
            return ((Fragment) object).getActivity();
        } else if (object instanceof android.app.Fragment) {
            return ((android.app.Fragment) object).getActivity();
        } else {
            return null;
        }
    }
    @TargetApi(23)
    private static boolean shouldShowRequestPermissionRationale(Object object, String perm) {
        if (object instanceof Activity) {
            return ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, perm);
        } else if (object instanceof Fragment) {
            return ((Fragment) object).shouldShowRequestPermissionRationale(perm);
        } else if (object instanceof android.app.Fragment) {
            return ((android.app.Fragment) object).shouldShowRequestPermissionRationale(perm);
        } else {
            return false;
        }
    }

    @TargetApi(23)
    private static void executePermissionsRequest(Object object, String[] perms, int requestCode) {
        checkCallingObjectSuitability(object);

        if (object instanceof Activity) {
            ActivityCompat.requestPermissions((Activity) object, perms, requestCode);
        } else if (object instanceof Fragment) {
            ((Fragment) object).requestPermissions(perms, requestCode);
        } else if (object instanceof android.app.Fragment) {
            ((android.app.Fragment) object).requestPermissions(perms, requestCode);
        }
    }

    static String[] getDeniedPermissions(Object o, String... permissions) {
        List<String> strings = new ArrayList<>(1);
        for (String permission : permissions)
            if (checkPermission(o, permission) != PackageManager.PERMISSION_GRANTED)
                strings.add(permission);
        return strings.toArray(new String[strings.size()]);
    }

    static int checkPermission(Object o, String permission) {
        return getActivity(o).checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid());
    }
    static <T extends Annotation> Method[] findMethodForRequestCode(Class<?> source, Class<T> annotation, int requestCode) {
        List<Method> methods = new ArrayList<>(1);
        for (Method method : source.getDeclaredMethods())
            if (method.isAnnotationPresent(annotation))
                if (isSameRequestCode(method, annotation, requestCode))
                    methods.add(method);
        return methods.toArray(new Method[methods.size()]);
    }

    static <T extends Annotation> boolean isSameRequestCode(Method method, Class<T> annotation, int requestCode) {
//        if (PermissionYes.class.equals(annotation))
//            return method.getAnnotation(PermissionYes.class).value() == requestCode;
//        else if (PermissionNo.class.equals(annotation))
//            return method.getAnnotation(PermissionNo.class).value() == requestCode;
        return false;
    }
    //callback(o, deniedPermissions.size() > 0 ? PermissionNo.class : PermissionYes.class, requestCode);
    static <T extends Annotation> void callback(Object o, Class<T> clazz, int requestCode) {
        Method[] methods = findMethodForRequestCode(o.getClass(), clazz, requestCode);
        try {
            for (Method method : methods) {
                if (!method.isAccessible())
                    method.setAccessible(true);
                method.invoke(o);
            }
        } catch (Exception e) {
            Log.e("AndPermission", "Callback methods fail.");
        }
    }
}
